﻿/*
 * Cratos Tools - Data Object
 * Cratos Data Object class
 * 
 * Copyright (c) 2011-2012 Draco
 * 
 * This file is part of Cratos.
 * 
 * Cratos is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * Cratos is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Cratos.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *       .==.        .==.
 *      //'^\\      //^'\\
 *     // ^ ^\(\__/)/^ ^^\\
 *    //^ ^^ ^/6  6\ ^^ ^ \\
 *   //^ ^^ ^/( .. )\^ ^ ^ \\
 *  // ^^ ^/\| V""V|/\^ ^ ^ \\
 * //^^/\/ /  '~~'  \ \/\^ ^ \\
 *  ------------------------------------
 *  HERE BE DRAGONS
 *  
 *  Johan Dercksen - johan@draco.co.za
 */
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Data;
using System.Windows;
using System.Reflection;
using System.Globalization;
using System.ComponentModel;
using System.Runtime.Remoting;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Collections.ObjectModel;
using System.Runtime.Serialization.Formatters.Binary;

using Microsoft.Practices.Unity;

using Cratos.Tools.General;

namespace Cratos.Tools.DataObject
{
	/// <summary>
	/// Cratos Data Object class
	/// </summary>
	[Serializable]
	public abstract class DOCratos : DOCratosNotifier, IDOCratos, IDisposable, ICloneable, IComparable
	{
		#region Class Constant declarations
		#endregion

		#region Class Event declarations
		#endregion

		#region Class Variable declarations
		private bool																					m_IsDisposed;
		private	bool																					m_IsChanged;
		private Collection<CratosError>												m_Errors	= new Collection<CratosError>();

		private Collection<DOCratosActionAttribute>						m_DOActions;
		private	Collection<DOCratosColumnAttribute>						m_DOColumns;
		private Collection<DOCratosCollectionFilterAttribute>	m_DOCollectionFilterColumns;
		private Collection<DOCratosEmbeddedAttribute>					m_DOEmbedded;
		private Collection<PropertyInfo>											allDOProperties;

		private Dictionary<DOCratosAction, List<CratosRule>>	currentRules;
		#endregion

		#region Class Constructors & Destructors
		/// <summary>
		/// Class Constructor
		/// </summary>
		public DOCratos()
		{
			// Set the relevant default properties
			this.DOGuid       = Guid.NewGuid();
			this.IsChanged    = false;
			this.IsLoading    = false;
			this.IsLoaded     = false;
			this.DBActionType = (DOCratosActionType)Enum.Parse(typeof(DOCratosActionType), DOCratosGlobal.DBActionType);

			// Create and setup the relevant objects
			this.AllProperties = this.GetType().GetProperties();
			if (this.AllDOProperties.Count > 0) { }

			// Clear the object
			this.Clear();
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or
		/// resetting unmanaged resources.
		/// </summary>
		public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }

		/// <summary>
		/// Dispose method doing the disposing
		/// </summary>
		/// <param name="isDisposing"></param>
		protected virtual void Dispose(bool isDisposing)
		{
			// Declare the required variables

			// Check to see if Dispose has already been called.
			if (!this.m_IsDisposed)
			{
				// Dispose the objects if required
				if (isDisposing)
				{
				}
			}

			// Set the disposed indicator
			this.m_IsDisposed = true;

			// Return to calling method
			return;
		}
		#endregion

		#region Class Properties
		/// <summary>
		/// Data Object GUID
		/// </summary>
		public Guid DOGuid { get; set; }

		/// <summary>
		/// All DO Properties
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("AllProperties")]
		protected PropertyInfo[] AllProperties { get; set; }

		/// <summary>
		/// Ignore Unity Indicator
		/// </summary>
		[DOCratosProperty("IgnoreUnity")]
		public bool IgnoreUnity { get; set; }

		/// <summary>
		/// Changed indicator
		/// </summary>
		[DOCratosProperty("IsChanged")]
		public bool IsChanged
		{
			get { return(this.m_IsChanged); }
			set { this.m_IsChanged = value; this.NotifyPropertyChanged("IsChanged"); }
		}

		/// <summary>
		/// Loading indicator
		/// </summary>
		[DOCratosProperty("IsLoading")]
		public bool IsLoading { get; set; }

		/// <summary>
		/// Loaded indicator
		/// </summary>
		[DOCratosProperty("IsLoaded")]
		public bool IsLoaded { get; set; }

		/// <summary>
		/// Cratos Data Object Action
		/// </summary>
		[DOCratosProperty("DOAction")]
		public DOCratosAction DOAction { get; set; }

		/// <summary>
		/// Cratos Data Object Action Result
		/// </summary>
		[DOCratosProperty("DOActionResult")]
		public DOCratosActionResult DOActionResult { get; set; }

		/// <summary>
		/// Cratos Data Object Custom Action name
		/// </summary>
		[DOCratosProperty("DOCustomActionName")]
		public string DOCustomActionName { get; set; }

		/// <summary>
		/// Database Connection Name
		/// </summary>
		[DOCratosProperty("DBConnectionName")]
		public string DBConnectionName { get; set; }

		/// <summary>
		/// Cratos Command Type
		/// </summary>
		[DOCratosProperty("DBActionType")]
		public DOCratosActionType DBActionType { get; set; }

		/// <summary>
		/// Collection of Errors
		/// </summary>
		[DOCratosProperty("Errors")]
		public Collection<CratosError> Errors
		{
			get { return (this.m_Errors); }
		}

		/// <summary>
		/// Cratos Data Object Actions
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("AllDOActions")]
		public Collection<DOCratosActionAttribute> AllDOActions
		{
			get
			{
				// Declare the required variables

				// Make sure we have an object to work with
				if (this.m_DOActions == null)
				{
					// Create the relevant object
					this.m_DOActions = new Collection<DOCratosActionAttribute>();

					// Retrieve all the Cratos Actions associated with the class
					DOCratosActionAttribute[] allActions = (DOCratosActionAttribute[])this.GetType().GetCustomAttributes(typeof(DOCratosActionAttribute), false);

					// Make sure we have data to work with
					if (allActions != null)
					{
						foreach (DOCratosActionAttribute currentAction in allActions)
						{
							if (currentAction.CratosAction != DOCratosAction.None) { this.m_DOActions.Add(currentAction); }
						}
					}
				}

				// Return to calling method
				return (this.m_DOActions);
			}
		}

		/// <summary>
		/// Cratos Data Object Columns
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("AllDOColumns")]
		public Collection<DOCratosColumnAttribute> AllDOColumns
		{
			get
			{
				// Declare the required variables
				DOCratosColumnAttribute[]						allColumns;
				DOCratosCollectionFilterAttribute[]	allCollectionFilters;

				// Make sure we have an object to work with
				if (this.m_DOColumns == null)
				{
					// Create the relevant object
					this.m_DOColumns									= new Collection<DOCratosColumnAttribute>();
					this.m_DOCollectionFilterColumns	= new Collection<DOCratosCollectionFilterAttribute>();

					// Process all the properties
					foreach (PropertyInfo currentProperty in this.AllProperties)
					{
						// Retrieve all the Cratos Column Attributes associated with the property
						allColumns						= (DOCratosColumnAttribute[])currentProperty.GetCustomAttributes(typeof(DOCratosColumnAttribute), false);
						allCollectionFilters	= (DOCratosCollectionFilterAttribute[])currentProperty.GetCustomAttributes(typeof(DOCratosCollectionFilterAttribute), false);

						// Make sure we have data to work with
						if (allColumns != null)
						{
							foreach (DOCratosColumnAttribute currentColumn in allColumns) { this.m_DOColumns.Add(currentColumn); }
						}

						if (allCollectionFilters != null)
						{
							foreach (DOCratosCollectionFilterAttribute currentFilter in allCollectionFilters) { this.m_DOCollectionFilterColumns.Add(currentFilter); }
						}
					}
				}

				// Return to calling method
				return(this.m_DOColumns);
			}
		}

		/// <summary>
		/// Cratos Data Object Collection Filter Columns
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("AllDOCollectionFilters")]
		public Collection<DOCratosCollectionFilterAttribute> AllDOCollectionFilters
		{
			get { return(this.m_DOCollectionFilterColumns); }
		}

		/// <summary>
		/// All Cratos Embedded Data Object Column Properties
		/// </summary>
		[DOCratosProperty("AllDOEmbedded")]
		public Collection<DOCratosEmbeddedAttribute> AllDOEmbedded
		{
			get
			{
				// Declare the required variables
				DOCratosEmbeddedAttribute[] allEmbedded;

				// Make sure we have an object to work with
				if (this.m_DOEmbedded == null)
				{
					// Create the relevant object
					this.m_DOEmbedded = new Collection<DOCratosEmbeddedAttribute>();

					// Process all the properties
					foreach (PropertyInfo currentProperty in this.AllProperties)
					{
						// Retrieve all the Cratos Column Attributes associated with the property
						allEmbedded = (DOCratosEmbeddedAttribute[])currentProperty.GetCustomAttributes(typeof(DOCratosEmbeddedAttribute), false);

						// Make sure we have data to work with
						if (allEmbedded != null)
						{
							foreach (DOCratosEmbeddedAttribute currentEmbedded in allEmbedded) { this.m_DOEmbedded.Add(currentEmbedded); }
						}
					}
				}

				// Return to calling method
				return (this.m_DOEmbedded);
			}
		}

		/// <summary>
		/// Cratos Data Object Actions
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("AllDOProperties")]
		public Collection<PropertyInfo> AllDOProperties
		{
			get
			{
				// Declare the required variables
				DOCratosPropertyAttribute[] returnDOProperties;

				// Make sure we have an object to work with
				if (this.allDOProperties == null)
				{
					// Create the relevant object
					this.allDOProperties = new Collection<PropertyInfo>();

					// Process all the current Properties
					foreach (PropertyInfo currentProperty in this.AllProperties)
					{
						// Retrieve all the Cratos Column Attributes associated with the property
						returnDOProperties = (DOCratosPropertyAttribute[])currentProperty.GetCustomAttributes(typeof(DOCratosPropertyAttribute), false);

						// Make sure we have data to work with
						if ((returnDOProperties == null) || (returnDOProperties.Length == 0))
						{
							this.allDOProperties.Add(currentProperty);
						}
					}
				}

				// Return to calling method
				return (this.allDOProperties);
			}
		}

		#region Action indicators
		/// <summary>
		/// Can Create indicator
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("CanCreate")]
		public bool CanCreate { get; protected set; }

		/// <summary>
		/// Can Retrieve indicator
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("CanRetrieve")]
		public bool CanRetrieve { get; protected set; }

		/// <summary>
		/// Can Update indicator
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("CanUpdate")]
		public bool CanUpdate { get; protected set; }

		/// <summary>
		/// Can Delete indicator
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("CanDelete")]
		public bool CanDelete { get; protected set; }
		#endregion

		/// <summary>
		/// Error Message
		/// </summary>
		[XmlIgnore]
		[DOCratosProperty("ErrorMessage")]
		public string ErrorMessage
		{
			get
			{
				// Declare the required variables
				StringBuilder returnValue = new StringBuilder();

				// Make sure we have errors
				if (this.Errors.Count > 0)
				{
					// Process all the errors
					foreach (CratosError currentError in this.Errors)
					{
						if (currentError.RuntimeException == null)
						{
							returnValue.AppendLine(string.Format("[{0}-{1}] [{2}] {3}", currentError.Date.ToString("dd/MM/yyyy HH:mm:ss"),
																																					currentError.Source,
																																					currentError.Severity.ToString(),
																																					currentError.Message));
						}
						else
						{
							returnValue.AppendLine(string.Format("[{0}-{1}] [{2}] {3}", currentError.Date.ToString("dd/MM/yyyy HH:mm:ss"),
																																					currentError.Source,
																																					currentError.Severity.ToString(),
																																					currentError.Message + "\n" + currentError.RuntimeException.ToString()));
						}
					}
				}
				else { returnValue.AppendLine("No errors found"); }

				// Return to calling method
				return (returnValue.ToString());
			}
		}
		#endregion

		#region Class Methods
		/// <summary>
		/// Reset all variables to their default values
		/// </summary>
		public virtual void Clear()
		{
			// Declare the required variables

			// Clear all current pending errors
			this.Errors.Clear();

			// Reset the relevant values to their default values
			this.m_IsChanged	= false;
			this.DOAction			= DOCratosAction.None;

			// Make sure we have data to work with
			if (this.AllProperties != null)
			{
				// Process all the Data Object Column Properties
				foreach (DOCratosColumnAttribute currentColumn in this.AllDOColumns)
				{
					// Determine if a default value has been specified
					if (currentColumn.DefaultValue == null)
					{
						// Set the default value of the property
						this.SetDefaultPropertyValue(this.GetType().GetProperty(currentColumn.Property));
					}
					else if (currentColumn.DbType == DbType.Decimal)
					{
						this.GetType().GetProperty(currentColumn.Property).SetValue(this, 0m, null);
					}
					else
					{
						this.GetType().GetProperty(currentColumn.Property).SetValue(this, currentColumn.DefaultValue, null);
					}
				}
			}

			// Return to calling method
			return;
		}

		/// <summary>
		/// Create a new copy of the object
		/// </summary>
		/// <returns></returns>
		public virtual object Clone()
		{
			// Return to calling method
			return (DOCratosGlobal.CloneObject(this));
		}

		/// <summary>
		/// Compare To method
		/// </summary>
		/// <param name="compareObj">Object to be compared to</param>
		/// <returns>A value that indicates the relative order of the objects being compared</returns>
		public virtual int CompareTo(object compareObj)
		{
			// Declare the required variables
			int																		returnValue = 0;
			PropertyInfo													currentPropInfo, objPropInfo;
			IEnumerable<DOCratosColumnAttribute>	linqObjects;

			// Make sure the object is of the correct type
			if (compareObj is IDOCratos)
			{
				// Retrieve all the keys for the current object
				linqObjects = from		currentObject in this.AllDOColumns
											where		currentObject.IsKey == true
											select	currentObject;

				// Handle all the Keys selected
				foreach (DOCratosColumnAttribute currentAttribute in linqObjects)
				{
					// Retrieve the relevant Property Information
					currentPropInfo	= this.GetType().GetProperty(currentAttribute.Property);
					objPropInfo			= compareObj.GetType().GetProperty(currentAttribute.Property);

					// Validate the Property
					returnValue += Math.Abs((int)currentPropInfo.GetValue(this, null).GetType().InvokeMember("CompareTo",
																																													BindingFlags.InvokeMethod, null,
																																													currentPropInfo.GetValue(this, null),
																																													new object[] { objPropInfo.GetValue(compareObj, null) },
																																													CultureInfo.CurrentCulture));
				}
			}
			else
			{
				throw new ArgumentException("[CompareTo] Object is not a Cratos Data Object");
			}

			// Return to calling method
			return (returnValue);
		}

		/// <summary>
		/// Equals
		/// </summary>
		/// <param name="obj">Object to compare</param>
		/// <returns>A bool indicating equality</returns>
		public override bool Equals(object obj)
		{
			// Call the base implementation
			return (base.Equals(obj));
		}

		/// <summary>
		/// Return Hash Code of current object
		/// </summary>
		/// <returns></returns>
		public override int GetHashCode()
		{
			// Call the base implementation
			return (base.GetHashCode());
		}

		/// <summary>
		/// Validate the object against the current action
		/// </summary>
		/// <returns>A Boolean value indicating the success of the method</returns>
		public virtual bool Validate()
		{
			// Declare the required variables
			bool returnValue = true;

			try
			{
				// Handle the relevant Action
				switch (this.DOAction)
				{
					case DOCratosAction.Create:
						#region Create Validation
						// Process all the Cratos Data Object columns
						foreach (DOCratosColumnAttribute currentColumn in this.AllDOColumns)
						{
							// Make sure we have a Create Attribute on the column
							DOCratosActionColumnAttribute[] actionAttributes = (DOCratosActionColumnAttribute[])this.GetType().GetProperty(currentColumn.Property).GetCustomAttributes(typeof(DOCratosActionColumnAttribute), false);

							// Process all the Attributes
							foreach (DOCratosActionColumnAttribute currentAttribute in actionAttributes)
							{
								// Make sure it is a create attribute
								if ((currentAttribute.CratosAction == DOCratosAction.Create) && (currentAttribute.IsMandatory))
								{
									// Validate the field
									if (!this.ValidateMandatoryField(currentColumn))
									{
										// Set the relevant field values
										returnValue = false;
										this.Errors.Add(new CratosError("DOCratos.Validate", CratosLogSeverity.ValidationError,
																										string.Format("{0} field is mandatory and no value was supplied\n[{1} - {2}]",
																																	currentColumn.Name, this.ToString(), this.DOAction.ToString())));
									}
								}
							}
						}
						#endregion

						break;

					case DOCratosAction.Retrieve:
						#region Retrieve Validation
						// Process all the Cratos Data Object columns
						foreach (DOCratosColumnAttribute currentColumn in this.AllDOColumns)
						{
							// Make sure we have a Create Attribute on the column
							DOCratosActionColumnAttribute[] actionAttributes = (DOCratosActionColumnAttribute[])this.GetType().GetProperty(currentColumn.Property).GetCustomAttributes(typeof(DOCratosActionColumnAttribute), false);

							// Process all the Attributes
							foreach (DOCratosActionColumnAttribute currentAttribute in actionAttributes)
							{
								// Make sure it is a create attribute
								if ((currentAttribute.CratosAction == DOCratosAction.Retrieve) && (currentAttribute.IsMandatory))
								{
									// Validate the field
									if (!this.ValidateMandatoryField(currentColumn))
									{
										// Set the relevant field values
										returnValue = false;
										this.Errors.Add(new CratosError("DOCratos.Validate", CratosLogSeverity.ValidationError,
																										string.Format("{0} field is mandatory and no value was supplied\n[{1} - {2}]",
																																	currentColumn.Name, this.ToString(), this.DOAction.ToString())));
									}
								}
							}
						}
						#endregion

						break;

					case DOCratosAction.Update:
						#region Update Validation
						// Process all the Cratos Data Object columns
						foreach (DOCratosColumnAttribute currentColumn in this.AllDOColumns)
						{
							// Make sure we have a Create Attribute on the column
							DOCratosActionColumnAttribute[] actionAttributes = (DOCratosActionColumnAttribute[])this.GetType().GetProperty(currentColumn.Property).GetCustomAttributes(typeof(DOCratosActionColumnAttribute), false);

							// Process all the Attributes
							foreach (DOCratosActionColumnAttribute currentAttribute in actionAttributes)
							{
								// Make sure it is a create attribute
								if ((currentAttribute.CratosAction == DOCratosAction.Update) && (currentAttribute.IsMandatory))
								{
									// Validate the field
									if (!this.ValidateMandatoryField(currentColumn))
									{
										// Set the relevant field values
										returnValue = false;
										this.Errors.Add(new CratosError("DOCratos.Validate", CratosLogSeverity.ValidationError,
																										string.Format("{0} field is mandatory and no value was supplied\n[{1} - {2}]",
																																	currentColumn.Name, this.ToString(), this.DOAction.ToString())));
									}
								}
							}
						}
						#endregion

						break;

					case DOCratosAction.Delete:
						#region Delete Validation
						// Process all the Cratos Data Object columns
						foreach (DOCratosColumnAttribute currentColumn in this.AllDOColumns)
						{
							// Make sure we have a Create Attribute on the column
							DOCratosActionColumnAttribute[] actionAttributes = (DOCratosActionColumnAttribute[])this.GetType().GetProperty(currentColumn.Property).GetCustomAttributes(typeof(DOCratosActionColumnAttribute), false);

							// Process all the Attributes
							foreach (DOCratosActionColumnAttribute currentAttribute in actionAttributes)
							{
								// Make sure it is a create attribute
								if ((currentAttribute.CratosAction == DOCratosAction.Delete) && (currentAttribute.IsMandatory))
								{
									// Validate the field
									if (!this.ValidateMandatoryField(currentColumn))
									{
										// Set the relevant field values
										returnValue = false;
										this.Errors.Add(new CratosError("DOCratos.Validate", CratosLogSeverity.ValidationError,
																										string.Format("{0} field is mandatory and no value was supplied\n[{1} - {2}]",
																																	currentColumn.Name, this.ToString(), this.DOAction.ToString())));
									}
								}
							}
						}
						#endregion

						break;

					case DOCratosAction.Custom:
						#region Custom Action Validation
						// Process all the Cratos Data Object columns
						foreach (DOCratosColumnAttribute currentColumn in this.AllDOColumns)
						{
							// Make sure we have a Create Attribute on the column
							DOCratosActionColumnAttribute[] actionAttributes = (DOCratosActionColumnAttribute[])this.GetType().GetProperty(currentColumn.Property).GetCustomAttributes(typeof(DOCratosActionColumnAttribute), false);

							// Process all the Attributes
							foreach (DOCratosActionColumnAttribute currentAttribute in actionAttributes)
							{
								// Make sure it is a create attribute
								if ((currentAttribute.CratosAction == DOCratosAction.Custom) && 
										(currentAttribute.CratosCustomActionName == this.DOCustomActionName) && (currentAttribute.IsMandatory))
								{
									// Validate the field
									if (!this.ValidateMandatoryField(currentColumn))
									{
										// Set the relevant field values
										returnValue = false;
										this.Errors.Add(new CratosError("DOCratos.Validate", CratosLogSeverity.ValidationError,
																										string.Format("{0} field is mandatory and no value was supplied\n[{1} - {2}]",
																																	currentColumn.Name, this.ToString(),  
																																	this.DOAction.ToString() + "-" + this.DOCustomActionName)));
									}
								}
							}
						}
						#endregion

						break;
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratos.Validate", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return (returnValue);
		}

		#region Rule Methods
		/// <summary>
		/// Initialize the DO Rules
		/// </summary>
		public virtual void InitializeRules()
		{
			// Declare the required variables

			// Creat a new Rule object if required
			if (this.currentRules == null) { this.currentRules = new Dictionary<DOCratosAction,List<CratosRule>>(); }

			// Clear all current Rules
			this.currentRules.Clear();

			// Return to calling method
			return;
		}

		/// <summary>
		/// Add Custom Rule to Data Object Validation
		/// </summary>
		/// <param name="doAction">Cratos Action</param>
		/// <param name="newRule">New Cratos Rule</param>
		public void AddRule(DOCratosAction doAction, CratosRule newRule)
		{
			// Declare the required variables
			List<CratosRule> newRuleList;

			// Add the Rule appropriately
			if (this.currentRules.ContainsKey(doAction))
			{
				// Add the Rule to the Rule List
				this.currentRules[doAction].Add(newRule);
			}
			else
			{
				// Create the Rule List and add the Cratos Rule
				newRuleList = new List<CratosRule>();
				newRuleList.Add(newRule);

				// Add the item to the Rule Dictionary
				this.currentRules.Add(doAction, newRuleList);
			}

			// Return to calling method
			return;
		}

		/// <summary>
		/// Validate all the relevant Rules
		/// </summary>
		/// <param name="doAction">Cratos Action</param>
		/// <returns>A boolean indicating the success of the method</returns>
		public bool ValidateRules(DOCratosAction doAction)
		{
			// Declare the required variables
			bool returnValue = true;
			List<CratosRule> actionRules;

			try
			{
				// Make sure we have Rules for the associated Action
				if ((this.currentRules != null) && (this.currentRules.ContainsKey(doAction)))
				{
					// Retrieve the Action rules
					actionRules = this.currentRules[doAction];

					// Process all the Rules
					foreach (CratosRule currentRule in actionRules)
					{
						if (!currentRule.IsValid)
						{
							this.Errors.Add(new CratosError("Validation Rules", CratosLogSeverity.ValidationError, currentRule.Description));
							returnValue = false;
						}
					}
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratos.ValidateRules", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return (returnValue);
		}
		#endregion

		/// <summary>
		/// Perform the required Action
		/// </summary>
		/// <returns>A boolean indicating the success of the method</returns>
		public virtual bool PerformAction()
		{
			// Declare the required variables
			bool						returnValue = false;
			DOCratosAction	currentAction;

			try
			{
				// Clear any pending errors
				this.Errors.Clear();

				// Save any data
				currentAction = this.DOAction;
				this.IsLoaded = false;

				// Setup the relevant data
				if (string.IsNullOrEmpty(this.DBConnectionName)) { this.DBConnectionName = DOCratosGlobal.DBConnectionName; }

				// Pass to the Entity Access layer
				if (this.IgnoreUnity) { returnValue = this.PerformNonUnityAccess(this); }
				else { returnValue = this.PerformEntityAccess(); }
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratos.PerformAction", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return(returnValue);
		}

		/// <summary>
		/// Load the Embedded data
		/// </summary>
		/// <param name="doProperty">Data Object Property</param>
		/// <param name="doCratos">Cratos Data Object</param>
		/// <returns>A boolean indicating the success of the method</returns>
		public bool LoadEmbedded(string doProperty, IDOCratos doCratos)
		{
			// Declare the required variables
			bool														returnValue = false;
			object													sourceData;
			DOCratosEmbeddedAttribute[]			embeddedAttributes;
			DOCratosEmbeddedKeyAttribute[]	embeddedKeyAttributes;

			try
			{
				// Clear any pending errors
				this.Errors.Clear();

				// Retrieve the relevant Embedded Object attributes
				embeddedAttributes		= (DOCratosEmbeddedAttribute[]) this.GetType().GetProperty(doProperty).GetCustomAttributes(typeof(DOCratosEmbeddedAttribute), false);
				embeddedKeyAttributes	= (DOCratosEmbeddedKeyAttribute[]) this.GetType().GetProperty(doProperty).GetCustomAttributes(typeof(DOCratosEmbeddedKeyAttribute), false);

				// Make sure we have the relevant required data
				if ((embeddedAttributes == null) || (embeddedAttributes.Length <= 0))
				{
					throw new Exception(string.Format("Embedded Attribute required on {0}.{1}", this.GetType().ToString(), doProperty));
				}

				// Make sure we can load the object dynamically
				if ((embeddedAttributes[0].LoadDynamically) && (this.IsLoaded))
				{
					// Set the relevant Embedded Key data
					foreach (DOCratosEmbeddedKeyAttribute currentKey in embeddedKeyAttributes)
					{
						// Retrieve the relevant Source Data
						sourceData = this.GetType().GetProperty(currentKey.ParentProperty).GetValue(this, null);

						// Update the Key Property
						doCratos.GetType().GetProperty(currentKey.EmbeddedKeyProperty).SetValue(doCratos, sourceData, null);
					}

					// Setup the relevant data
					doCratos.DBConnectionName = DOCratosGlobal.DBConnectionName;
					doCratos.DOAction					= DOCratosAction.Retrieve;

					// Pass to the Entity Access layer
					returnValue = this.PerformEntityAccess();
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratos.LoadEmbedded", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return(returnValue);
		}

		/// <summary>
		/// Load the Embedded Collection data
		/// </summary>
		/// <param name="doProperty">Data Object Property</param>
		/// <param name="doCratos">Cratos Data Object</param>
		/// <returns>A boolean indicating the success of the method</returns>
		public bool LoadEmbeddedCollection(string doProperty, IDOCratosCollection<IDOCratos> doCratos)
		{
			// Declare the required variables
			bool														returnValue = false;
			object													sourceData;
			DOCratosEmbeddedAttribute[]			embeddedAttributes;
			DOCratosEmbeddedKeyAttribute[]	embeddedKeyAttributes;

			try
			{
				// Clear any pending errors
				this.Errors.Clear();

				// Retrieve the relevant Embedded Object attributes
				embeddedAttributes		= (DOCratosEmbeddedAttribute[])this.GetType().GetProperty(doProperty).GetCustomAttributes(typeof(DOCratosEmbeddedAttribute), false);
				embeddedKeyAttributes = (DOCratosEmbeddedKeyAttribute[])this.GetType().GetProperty(doProperty).GetCustomAttributes(typeof(DOCratosEmbeddedKeyAttribute), false);

				// Make sure we have the relevant required data
				if ((embeddedAttributes == null) || (embeddedAttributes.Length <= 0))
				{
					throw new Exception(string.Format("Embedded Attribute required on {0}.{1}", this.GetType().ToString(), doProperty));
				}

				// Make sure we can load the object dynamically
				if ((embeddedAttributes[0].LoadDynamically) && (this.IsLoaded))
				{
					// Set the relevant Embedded Key data
					foreach (DOCratosEmbeddedKeyAttribute currentKey in embeddedKeyAttributes)
					{
						// Retrieve the relevant Source Data
						sourceData = this.GetType().GetProperty(currentKey.ParentProperty).GetValue(this, null);

						// Update the Key Property
						doCratos.GetType().GetProperty(currentKey.EmbeddedKeyProperty).SetValue(doCratos, sourceData, null);
					}

					// Setup the relevant data
					doCratos.DataObject.DBConnectionName = DOCratosGlobal.DBConnectionName;
					doCratos.CollectionAction				     = DOCratosCollectionAction.Retrieve;

					// Pass to the Entity Access layer
					returnValue = doCratos.PerformAction();
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratos.LoadEmbeddedCollection", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return (returnValue);
		}

		/// <summary>
		/// Display the current Errors in the error collection
		/// </summary>
		/// <param name="appTitle">Application Title</param>
		public virtual void DisplayError(string appTitle)
		{
			// Declare the required variables
			StringBuilder errorMessage;

			// Build the error message
			errorMessage = new StringBuilder();
			foreach (CratosError currentError in this.Errors) 
			{
				errorMessage.AppendLine(string.Format("[{0} - {1}] [{2}] {3}\n{4}", currentError.Date.ToString("dd/MM/yyyy HH:mm:ss"), 
																																						currentError.Source,
																																						currentError.Severity.ToString(),
																																						currentError.Message,
																																						currentError.RuntimeException.ToString()));
			}

			// Display the error to the user
			MessageBox.Show(errorMessage.ToString(), appTitle, MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK);

			// Return to calling method
			return;
		}

		/// <summary>
		/// Create Serialize Stream from object
		/// </summary>
		/// <returns>
		/// A byte array of the serialized object
		/// </returns>
		public virtual byte[] CreateSerializeStream()
		{
			// Return to calling method
			return (DOCratosGlobal.BinSerialize(this));
		}

		/// <summary>
		/// Create Object from a Serialized object
		/// </summary>
		/// <param name="objectStream">A byte array representing the serialized object</param>
		/// <returns>
		/// The recreated object
		/// </returns>
		public abstract IDOCratos CreateObjectFromStream(byte[] objectStream);
		#endregion

		#region Class Support Methods
		private bool PerformEntityAccess()
		{
			// Declare the required variables
			bool								returnValue  = false;
			ICratosEntityAccess entityAccess = null;
			IDOCratos						doCratos;

			try
			{
				// Clone the current object
				doCratos = ((IDOCratos) this.Clone());

				// Make sure the type is registered
				if (DOCratosGlobal.UnityContainer.IsRegistered<ICratosEntityAccess>(DOCratosGlobal.EntityAccessMethod))
				{
					// Create the relevant type
					entityAccess = DOCratosGlobal.UnityContainer.Resolve<ICratosEntityAccess>(DOCratosGlobal.EntityAccessMethod);
				}
				else { throw new Exception("Cratos Entity Access type not registered"); }

				// Make sure we have an Entity object to work with
				if (entityAccess != null)
				{
					// Execute the relevant Action
					if (entityAccess.PerformAction(ref doCratos))
					{
						// Update the local Data
						this.UpdateLocalData(doCratos);

						// Set the successful return value
						returnValue = true;
					}
					else
					{
						foreach (CratosError currentError in entityAccess.Errors) { this.Errors.Add(currentError); }
					}
				}
				else { throw new Exception("Failed to create the required Cratos Access Type"); }
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratos.PerformEntityAccess", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return (returnValue);
		}

		private bool PerformNonUnityAccess(IDOCratos doCratos)
		{
			// Declare the required variables
			bool								returnValue = false;
			ICratosEntityAccess entityAccess = null;
			ObjectHandle				tempObject;

			try
			{
				// Create and Setup the Eantity Access Layer
				tempObject								= Activator.CreateInstance("Cratos.Server.Entity", "Cratos.Server.Entity.CratosEAL");
				entityAccess							= ((ICratosEntityAccess) tempObject.Unwrap());
				entityAccess.IgnoreUnity	= true;

				// Execute the relevant Action
				if (entityAccess.PerformAction(ref doCratos)) { returnValue = true; }
				else
				{
					foreach (CratosError currentError in entityAccess.Errors) { this.Errors.Add(currentError); }
				}
			}
			catch (Exception runtimeException)
			{
				// Create and setup a new error
				this.Errors.Add(new CratosError("DOCratos.PerformNonUnityAccess", CratosLogSeverity.Exception,
																				"Runtime error ocurred", runtimeException));
			}

			// Return to calling method
			return (returnValue);
		}

		private void SetDefaultPropertyValue(PropertyInfo currentProperty)
		{
			// Declare the required variables

			// Handle the Property Data Type
			switch (currentProperty.PropertyType.ToString())
			{
				case "Int16":
				case "Int32":
				case "Int64":
					currentProperty.SetValue(this, 0, null);
					break;

				case "decimal":
					currentProperty.SetValue(this, 0m, null);
					break;

				case "String":
				case "string":
					currentProperty.SetValue(this, "", null);
					break;

				case "DateTime":
					currentProperty.SetValue(this, new DateTime(1753, 1, 1), null);
					break;

				default:
					currentProperty.SetValue(this, null, null);
					break;
			}

			// Return to calling method
			return;
		}

		private bool ValidateMandatoryField(DOCratosColumnAttribute valField)
		{
			// Declare the required variables
			bool returnValue = true;

			// Handle the Data Type of the field
			switch (valField.DbType)
			{
				case DbType.Int16:
				case DbType.Int32:
				case DbType.Int64:
					returnValue = (Convert.ToInt64(this.GetType().GetProperty(valField.Property).GetValue(this, null), CultureInfo.CurrentCulture) != 0);
					break;

				case DbType.Decimal:
					returnValue = (Convert.ToDecimal(this.GetType().GetProperty(valField.Property).GetValue(this, null), CultureInfo.CurrentCulture) != 0);
					break;

				case DbType.String:
					returnValue = (!string.IsNullOrEmpty(Convert.ToString(this.GetType().GetProperty(valField.Property).GetValue(this, null), CultureInfo.CurrentCulture)));
					break;

				case DbType.DateTime:
					returnValue = (Convert.ToDateTime(this.GetType().GetProperty(valField.Property).GetValue(this, null),
																						CultureInfo.CurrentCulture).Year > 1753);
					break;

				default:
					returnValue = (this.GetType().GetProperty(valField.Property).GetValue(this, null) != null);
					break;
			}

			// Return to calling method
			return (returnValue);
		}

		private void UpdateLocalData(IDOCratos sourceDO)
		{
			// Declare the required variables
			PropertyInfo sourceProperty = null;
			PropertyInfo destinationProperty;

			try
			{
				// Set the source DO to loading
				sourceDO.IsLoading = true;

				// Process all the Source DO
				foreach (DOCratosColumnAttribute sourceCol in sourceDO.AllDOColumns)
				{
					// Find the Source and Destination Properties
					sourceProperty      = sourceDO.GetType().GetProperty(sourceCol.Property);
					destinationProperty = this.GetType().GetProperty(sourceCol.Property);

					// Process if both have been found
					if ((sourceProperty != null) && (destinationProperty != null))
					{
						destinationProperty.SetValue(this, sourceProperty.GetValue(sourceDO, null), null);
					}
				}

				// Process all the Embeddded objects
				foreach (DOCratosEmbeddedAttribute currentEmbedded in sourceDO.AllDOEmbedded)
				{
					// Find the Source and Destination Properties
					sourceProperty			= sourceDO.GetType().GetProperty(currentEmbedded.Property);
					destinationProperty = this.GetType().GetProperty(currentEmbedded.Property);

					// Process if both have been found
					if ((sourceProperty != null) && (destinationProperty != null))
					{
						destinationProperty.SetValue(this, sourceProperty.GetValue(sourceDO, null), null);
					}
				}

				// Update the relevant properties
				this.DOAction       = DOCratosAction.None;
				this.DOActionResult = sourceDO.DOActionResult;
				this.IsLoaded       = sourceDO.DOActionResult == DOCratosActionResult.Success ? true : false;
				sourceDO.IsLoading	= false;
			}
			catch (Exception runtimeException)
			{
				if (sourceProperty == null) { throw runtimeException; }
				else
				{
					throw new Exception(string.Format("Property not found : {0}", sourceProperty.Name), runtimeException);
				}
			}

			// Return to calling method
			return;
		}
		#endregion
	}
}
